#import "MEvaluator.h"
#import "MEvaluation.h"
#import "MExpr.h"
#import "MMathLink.h"
#import "MPacket.h"
#import "mathlink.h"

#import <unistd.h>

@interface MEvaluator (MEvaluatorPrivateMethods)

- (BOOL) _sendNextQueuedEvaluation;

@end


NSString* evaluatorPath()
{
	NSURL* theURL;
	if(0 && LSFindApplicationForInfo('OMEG', (CFStringRef)  @"com.wolfram.mathematica",
		NULL, NULL, (CFURLRef*) &theURL) == noErr)
	{
		return [[[theURL autorelease] path] stringByAppendingPathComponent: @"Contents/MacOS/MathKernel"];
	}
	else
	{
		NSFileManager* fm = [NSFileManager defaultManager];
		NSString* paths[] = {
			@"/Applications/Mathematica.app/Contents/MacOS/MathKernel",
			@"/Applications/Mathematica 6.0.app/Contents/MacOS/MathKernel",
			@"/Applications/Mathematica 5.2.app/Contents/MacOS/MathKernel",
			@"/Applications/Mathematica 5.1.app/Contents/MacOS/MathKernel",
			@"/Applications/Mathematica 5.0.app/Contents/MacOS/MathKernel",
			@"/Applications/Mathematica 4.2.app/Contents/MacOS/MathKernel",
			@"/Applications/Mathematica 4.1.app/Contents/MacOS/MathKernel"
		};
		
		size_t i;
		for(i = 0; i < sizeof(paths)/sizeof(paths[0]); i++)
		{
			if([fm fileExistsAtPath: paths[i]])
				return paths[i];
		}
		return nil;
	}
}

@implementation MEvaluator

+ (id) evaluator
{
	NSString* path = evaluatorPath();
	return [MEvaluator evaluatorWithPath: path];
}

+ (id) evaluatorWithPath: (NSString*) path
{
	NSArray* argv = [NSArray arrayWithObjects: @"-linkmode", @"launch", @"-linkname", 
		[NSString stringWithFormat: @"'%@' -mathlink", path], nil];
	
	MMathLink* link = [MMathLink linkWithArgv: argv];
	if(link)
		return [MEvaluator evaluatorWithLink: link];
	return nil;
}

+ (id) evaluatorWithLink: (MMathLink*) link
{
	return [[[MEvaluator alloc] initWithLink: link] autorelease];
}

- (id) initWithLink: (MMathLink*) link
{
	self = [super init];
	_evaluations = [[NSMutableArray alloc] init];
	[self setLink: link];
	return self;
}

- (void) dealloc
{
	[self setDelegate: nil];
	[self setLink: nil];
	[_evaluations release];
	[super dealloc];
}

- (MMathLink*) link
{
	return _link;
}

- (CFRunLoopRef) runLoop
{
	return [_link runLoop];
}

- (void) setLink: (MMathLink*) link
{
	[self setLink: link runLoop: NULL];
}

- (void) setLink: (MMathLink*) link runLoop: (CFRunLoopRef) runLoop
{
	if(link != _link)
	{
		[_link setRunLoop: NULL];
		[_link autorelease];
		
		_link = [link retain];
		[_link setRunLoop: runLoop ? runLoop : CFRunLoopGetCurrent()];
	}
}

- (id) delegate
{
	return _delegate;
}

- (void) setDelegate: (id) delegate
{
	[_delegate autorelease];
	_delegate = [delegate retain];
}

- (MExpr*) evaluate: (MExpr*) expr asynchronously: (BOOL) async
{
	MEvaluation* eval = [[[MEvaluation alloc] initWithEvaluator: self expr: expr] autorelease];
	[_evaluations addObject: eval];
	if([_evaluations count] == 1)
		[self _sendNextQueuedEvaluation];
	
	if(async)
		return nil;
	
	while([self currentEvaluation])
	{
		[_link handlePacket];
		usleep(10000);
	}
	return [eval result];
}

- (MExpr*) evaluateInputForm: (NSString*) inputForm asynchronously: (BOOL) async
{
	MExpr* expr = [MExpr exprWithFunctionName: @"ToExpression",
		[MExpr exprWithString: inputForm], nil];
	
	return [self evaluate: expr asynchronously: async];
}

- (MEvaluation*) currentEvaluation
{
	if([_evaluations count] == 0)
		return nil;
	
	return [_evaluations objectAtIndex: 0];
}

- (NSArray*) queuedEvaluations
{
	return _evaluations;
}

@end

@implementation MEvaluator (MEvaluatorPrivateMethods)

- (void) handlePacket: (int) packet link: (MMathLink*) link
{
	MEvaluation* eval = [[[_evaluations objectAtIndex: 0] retain] autorelease];
	MPacket* pkt = nil;
	
	switch(packet)
	{
		case TEXTPKT:
			pkt = [MPacket textPacket: _link];
			break;
		
		case RETURNPKT:
			[eval setResult: [MExpr exprWithLink: _link]];
			if([_delegate respondsToSelector: @selector(evaluationDidFinish:)])
				[_delegate evaluationDidFinish: eval];
			
			[_evaluations removeObjectAtIndex: 0];
			if([_evaluations count] > 0)
				[self _sendNextQueuedEvaluation];
			break;
		
		case MESSAGEPKT:
			pkt = [MPacket messagePacket: _link];
			break;
		
		case INPUTNAMEPKT:
			[eval setInputName: [_link string]];
			
			if([_delegate respondsToSelector: @selector(evaluationInputNameDidChange:)])
				[_delegate evaluationInputNameDidChange: eval];
			break;
		
		case DISPLAYPKT:
			pkt = [eval currentDisplayPacketCreating: YES];
			[(MDisplayPacket*)pkt appendData: [_link string]];
			break;
		
		case DISPLAYENDPKT:
			pkt = [eval currentDisplayPacketCreating: YES];
			[(MDisplayPacket*)pkt appendData: [_link string]];
			[(MDisplayPacket*)pkt setEnded: YES];
			break;
	}
	
	if(pkt)
	{
		[eval addPacket: pkt];
		
		if([_delegate respondsToSelector: @selector(evaluation:didReceivePacket:)])
			[_delegate evaluation: eval didReceivePacket: pkt];
	}
	
	/*
	switch(packet)
	{
		case CALLPKT:
			if([_delegate respondsToSelector: @selector(callPacket:)])
				[_delegate callPacket: self];
			break;
			
		case EVALUATEPKT:
			if([_delegate respondsToSelector: @selector(evaluatePacket:)])
				[_delegate evaluatePacket: self];
			break;
		
		case RETURNPKT:
			if([_delegate respondsToSelector: @selector(returnPacket:)])
				[_delegate returnPacket: self];
			break;
		
		case INPUTNAMEPKT:
			if([_delegate respondsToSelector: @selector(inputNamePacket:)])
				[_delegate inputNamePacket: self];
			break;
		
		case ENTERTEXTPKT:
			if([_delegate respondsToSelector: @selector(enterTextPacket:)])
				[_delegate enterTextPacket: self];
			break;
		
		case ENTEREXPRPKT:
			if([_delegate respondsToSelector: @selector(enterExprPacket)])
				[_delegate enterExprPacket: self];
			break;
		
		case OUTPUTNAMEPKT:
			if([_delegate respondsToSelector: @selector(outputNamePacket:)])
				[_delegate outputNamePacket: self];
			break;
		
		case RETURNTEXTPKT:
			if([_delegate respondsToSelector: @selector(returnTextPacket:)])
				[_delegate returnTextPacket: self];
			break;
		
		case RETURNEXPRPKT:
			if([_delegate respondsToSelector: @selector(returnExprPacket:)])
				[_delegate returnExprPacket: self];
			break;
		
		case DISPLAYPKT:
			if([_delegate respondsToSelector: @selector(displayPacket:)])
				[_delegate displayPacket: self];
			break;
		
		case DISPLAYENDPKT:
			if([_delegate respondsToSelector: @selector(displayEndPacket:)])
				[_delegate displayEndPacket: self];
			break;
			
		case MESSAGEPKT:
			if([_delegate respondsToSelector: @selector(messagePacket:)])
				[_delegate messagePacket: self];
			break;
		
		case TEXTPKT:
			if([_delegate respondsToSelector: @selector(textPacket:)])
				[_delegate textPacket: self];
			break;
		
		case INPUTPKT:
			if([_delegate respondsToSelector: @selector(inputPacket:)])
				[_delegate inputPacket: self];
			break;
		
		case INPUTSTRPKT:
			if([_delegate respondsToSelector: @selector(inputStrPacket:)])
				[_delegate inputStrPacket: self];
			break;
		
		case MENUPKT:
			if([_delegate respondsToSelector: @selector(menuPacket:)])
				[_delegate menuPacket: self];
			break;
		
		case SYNTAXPKT:
			if([_delegate respondsToSelector: @selector(syntaxPacket:)])
				[_delegate syntaxPacket: self];
			break;
		
		case SUSPENDPKT:
			if([_delegate respondsToSelector: @selector(suspendPacket:)])
				[_delegate suspendPacket: self];
			break;
		
		case RESUMEPKT:
			if([_delegate respondsToSelector: @selector(resumePacket:)])
				[_delegate resumePacket: self];
			break;
		
		case BEGINDLGPKT:
			if([_delegate respondsToSelector: @selector(beginDialogPacket:)])
				[_delegate beginDialogPacket: self];
			break;
		
		case ENDDLGPKT:
			if([_delegate respondsToSelector: @selector(endDialogPacket:)])
				[_delegate endDialogPacket: self];
			break;
		
		default:
			if(packet >= FIRSTUSERPKT && packet <= LASTUSERPKT)
				if([_delegate respondsToSelector: @selector(userPacket:packet:)])
					[_delegate userPacket: self packet: packet];
			else
				if([_delegate respondsToSelector: @selector(unknownPacket:packet:)])
					[_delegate unknownPacket: self packet: packet];
	}
	*/
}

- (BOOL) _sendNextQueuedEvaluation
{
	MEvaluation* eval = [_evaluations objectAtIndex: 0];
	
	if([_link newPacket]
		&& [_link putFunction: @"EvaluatePacket" length: 1]
		&& [[eval expr] put: _link]
		&& [_link endPacket])
	{
		if([_delegate respondsToSelector: @selector(evaluationDidStart:)])
			[_delegate evaluationDidStart: eval];
	
		return [_link flush];
	}
	return NO;
}

@end
